<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>SPI通信协议模拟器 - Driver_SPI_SwapByte演示</title>
    <script src="https://cdn.tailwindcss.com"></script>
    <link href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css" rel="stylesheet">
    <script>
        tailwind.config = {
            theme: {
                extend: {
                    colors: {
                        primary: '#3B82F6',
                        secondary: '#10B981',
                        accent: '#8B5CF6',
                        danger: '#EF4444',
                        dark: '#1E293B',
                        light: '#F8FAFC'
                    },
                    fontFamily: {
                        mono: ['Consolas', 'Monaco', 'monospace'],
                    },
                }
            }
        }
    </script>
    <style type="text/tailwindcss">
        @layer utilities {
            .content-auto {
                content-visibility: auto;
            }
            .signal-line {
                @apply h-2 my-4 rounded-full transition-all duration-500;
            }
            .register-cell {
                @apply border border-gray-300 rounded p-2 text-center transition-all duration-300;
            }
            .register-cell-active {
                @apply border-primary bg-primary/10 scale-105;
            }
            .spi-step {
                @apply opacity-50 transition-all duration-500;
            }
            .spi-step-active {
                @apply opacity-100 font-bold;
            }
        }
    </style>
</head>
<body class="bg-gray-50 min-h-screen">
    <header class="bg-dark text-white p-4 shadow-lg">
        <div class="container mx-auto">
            <h1 class="text-2xl md:text-3xl font-bold flex items-center">
                <i class="fa fa-exchange mr-3 text-primary"></i>
                SPI通信协议模拟器
            </h1>
            <p class="text-gray-300 mt-1">演示Driver_SPI_SwapByte函数的底层工作原理</p>
        </div>
    </header>

    <main class="container mx-auto p-4 md:p-6">
        <section class="bg-white rounded-xl shadow-md p-6 mb-8">
            <h2 class="text-xl font-bold mb-4 text-dark">
                <i class="fa fa-info-circle text-primary mr-2"></i>什么是SPI通信？
            </h2>
            <p class="mb-4">SPI（Serial Peripheral Interface）是一种同步串行通信协议，采用全双工通信方式。这意味着在通信时，数据的发送和接收是同时进行的。</p>
            <p>在SPI通信中，<strong>Driver_SPI_SwapByte</strong>函数实际上执行的是"数据交换"操作 - 每次调用都会同时发送一个字节并接收一个字节，这就是为什么读取和写入使用同一个函数的原因。</p>
        </section>

        <div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
            <!-- 控制面板 -->
            <section class="bg-white rounded-xl shadow-md p-6">
                <h2 class="text-xl font-bold mb-4 text-dark">
                    <i class="fa fa-cogs text-primary mr-2"></i>操作控制
                </h2>
                
                <div class="mb-6">
                    <label class="block text-sm font-medium text-gray-700 mb-1">操作类型</label>
                    <div class="flex space-x-3">
                        <button id="readBtn" class="px-4 py-2 bg-primary text-white rounded hover:bg-primary/90 transition">
                            <i class="fa fa-arrow-down mr-1"></i>读取寄存器
                        </button>
                        <button id="writeBtn" class="px-4 py-2 bg-secondary text-white rounded hover:bg-secondary/90 transition">
                            <i class="fa fa-arrow-up mr-1"></i>写入寄存器
                        </button>
                    </div>
                </div>
                
                <div class="mb-6">
                    <label for="regAddress" class="block text-sm font-medium text-gray-700 mb-1">寄存器地址 (0-15)</label>
                    <input type="number" id="regAddress" min="0" max="15" value="3" 
                        class="w-full px-3 py-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-primary">
                </div>
                
                <div id="writeValueContainer" class="mb-6 hidden">
                    <label for="writeValue" class="block text-sm font-medium text-gray-700 mb-1">写入值 (0-255)</label>
                    <input type="number" id="writeValue" min="0" max="255" value="0xAA" 
                        class="w-full px-3 py-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-primary">
                </div>
                
                <button id="startBtn" class="w-full py-3 bg-accent text-white rounded-lg hover:bg-accent/90 transition font-bold text-lg">
                    <i class="fa fa-play mr-2"></i>开始演示
                </button>
                
                <button id="resetBtn" class="w-full py-2 mt-3 bg-gray-200 text-gray-700 rounded-lg hover:bg-gray-300 transition">
                    <i class="fa fa-refresh mr-1"></i>重置
                </button>
            </section>
            
            <!-- 通信过程演示 -->
            <section class="bg-white rounded-xl shadow-md p-6 lg:col-span-2">
                <h2 class="text-xl font-bold mb-4 text-dark">
                    <i class="fa fa-signal text-primary mr-2"></i>通信过程演示
                </h2>
                
                <div class="mb-8">
                    <div class="flex justify-between items-center mb-2">
                        <div class="font-medium">主机 (MCU)</div>
                        <div class="text-gray-500 text-sm">片选 (CS)</div>
                        <div class="font-medium">从机 (SI24R1)</div>
                    </div>
                    
                    <div class="flex items-center">
                        <div class="w-1/3 h-24 border border-gray-300 rounded-lg flex items-center justify-center bg-gray-50">
                            <div id="mcuData" class="font-mono text-xl">0x00</div>
                        </div>
                        
                        <div class="w-1/3 flex flex-col items-center mx-4">
                            <div id="csLine" class="signal-line bg-danger w-full"></div>
                            <div id="sclkLine" class="signal-line bg-gray-400 w-full"></div>
                            <div id="mosiLine" class="signal-line bg-primary w-full"></div>
                            <div id="misoLine" class="signal-line bg-secondary w-full"></div>
                            <div class="grid grid-cols-4 gap-2 w-full text-xs text-gray-500 mt-1">
                                <div>CS</div>
                                <div>SCLK</div>
                                <div>MOSI</div>
                                <div>MISO</div>
                            </div>
                        </div>
                        
                        <div class="w-1/3 h-24 border border-gray-300 rounded-lg flex items-center justify-center bg-gray-50">
                            <div id="slaveData" class="font-mono text-xl">0x00</div>
                        </div>
                    </div>
                </div>
                
                <!-- 步骤显示 -->
                <div class="mb-6">
                    <h3 class="font-bold mb-2">通信步骤</h3>
                    <div class="space-y-2">
                        <div id="step1" class="spi-step">
                            <span class="inline-block w-6 h-6 bg-gray-200 rounded-full text-center mr-2">1</span>
                            片选信号拉低 (CS = 低电平)，选中从设备
                        </div>
                        <div id="step2" class="spi-step">
                            <span class="inline-block w-6 h-6 bg-gray-200 rounded-full text-center mr-2">2</span>
                            发送寄存器地址 (通过MOSI线)，同时接收从机返回的应答 (通过MISO线)
                        </div>
                        <div id="step3" class="spi-step">
                            <span class="inline-block w-6 h-6 bg-gray-200 rounded-full text-center mr-2">3</span>
                            <span id="step3Text">发送 dummy 数据 (0x00)，同时接收寄存器的值 (通过MISO线)</span>
                        </div>
                        <div id="step4" class="spi-step">
                            <span class="inline-block w-6 h-6 bg-gray-200 rounded-full text-center mr-2">4</span>
                            片选信号拉高 (CS = 高电平)，结束通信
                        </div>
                    </div>
                </div>
                
                <!-- 结果显示 -->
                <div id="resultContainer" class="p-4 bg-gray-50 rounded-lg hidden">
                    <h3 class="font-bold mb-2">操作结果</h3>
                    <div id="resultText" class="font-mono"></div>
                </div>
                
                <!-- 通信日志 -->
                <div class="mt-6">
                    <h3 class="font-bold mb-2">通信日志</h3>
                    <div id="logContainer" class="h-32 overflow-y-auto p-3 bg-gray-900 text-gray-300 rounded-lg font-mono text-sm"></div>
                </div>
            </section>
        </div>
        
        <!-- 寄存器状态 -->
        <section class="bg-white rounded-xl shadow-md p-6 mt-6">
            <h2 class="text-xl font-bold mb-4 text-dark">
                <i class="fa fa-database text-primary mr-2"></i>从机寄存器状态
            </h2>
            
            <div class="grid grid-cols-2 sm:grid-cols-4 md:grid-cols-8 lg:grid-cols-16 gap-3" id="registersContainer">
                <!-- 寄存器将通过JavaScript动态生成 -->
            </div>
        </section>
    </main>

    <footer class="bg-dark text-white p-4 mt-10">
        <div class="container mx-auto text-center text-gray-400 text-sm">
            SPI通信协议模拟器 &copy; 2023 - 演示Driver_SPI_SwapByte函数的工作原理
        </div>
    </footer>

    <script>
        // 初始化寄存器数据
        let registers = [];
        for (let i = 0; i < 16; i++) {
            // 随机初始化一些值，便于演示
            registers.push(Math.floor(Math.random() * 256));
        }
        
        // DOM元素
        const readBtn = document.getElementById('readBtn');
        const writeBtn = document.getElementById('writeBtn');
        const startBtn = document.getElementById('startBtn');
        const resetBtn = document.getElementById('resetBtn');
        const regAddressInput = document.getElementById('regAddress');
        const writeValueInput = document.getElementById('writeValue');
        const writeValueContainer = document.getElementById('writeValueContainer');
        const registersContainer = document.getElementById('registersContainer');
        const logContainer = document.getElementById('logContainer');
        const resultContainer = document.getElementById('resultContainer');
        const resultText = document.getElementById('resultText');
        const step3Text = document.getElementById('step3Text');
        
        // 信号线路元素
        const csLine = document.getElementById('csLine');
        const sclkLine = document.getElementById('sclkLine');
        const mosiLine = document.getElementById('mosiLine');
        const misoLine = document.getElementById('misoLine');
        
        // 数据显示元素
        const mcuData = document.getElementById('mcuData');
        const slaveData = document.getElementById('slaveData');
        
        // 步骤元素
        const steps = [
            document.getElementById('step1'),
            document.getElementById('step2'),
            document.getElementById('step3'),
            document.getElementById('step4')
        ];
        
        // 操作类型（读/写）
        let operationType = 'read';
        
        // 初始化寄存器显示
        function initRegisters() {
            registersContainer.innerHTML = '';
            registers.forEach((value, index) => {
                const regElement = document.createElement('div');
                regElement.className = 'register-cell';
                regElement.id = `reg-${index}`;
                regElement.innerHTML = `
                    <div class="text-xs text-gray-500">REG${index}</div>
                    <div class="font-mono font-bold">0x${value.toString(16).toUpperCase().padStart(2, '0')}</div>
                `;
                registersContainer.appendChild(regElement);
            });
        }
        
        // 添加日志
        function addLog(message) {
            const timestamp = new Date().toLocaleTimeString();
            const logEntry = document.createElement('div');
            logEntry.innerHTML = `<span class="text-gray-500">[${timestamp}]</span> ${message}`;
            logContainer.appendChild(logEntry);
            logContainer.scrollTop = logContainer.scrollHeight;
        }
        
        // 模拟时钟信号
        function simulateClock(signalElement, duration = 100) {
            return new Promise(resolve => {
                // 时钟信号闪烁几次模拟时钟脉冲
                let count = 0;
                const interval = setInterval(() => {
                    signalElement.classList.toggle('bg-gray-600');
                    count++;
                    if (count >= 16) { // 8个时钟周期（每个周期高低各一次）
                        clearInterval(interval);
                        signalElement.className = 'signal-line bg-gray-400 w-full';
                        resolve();
                    }
                }, duration / 16);
            });
        }
        
        // 高亮显示当前步骤
        function highlightStep(stepIndex) {
            steps.forEach((step, index) => {
                if (index === stepIndex) {
                    step.classList.add('spi-step-active');
                    step.querySelector('span').classList.remove('bg-gray-200');
                    step.querySelector('span').classList.add('bg-primary', 'text-white');
                } else {
                    step.classList.remove('spi-step-active');
                    step.querySelector('span').classList.add('bg-gray-200');
                    step.querySelector('span').classList.remove('bg-primary', 'text-white');
                }
            });
        }
        
        // 执行SPI通信模拟
        async function simulateSPI() {
            // 禁用按钮防止重复操作
            startBtn.disabled = true;
            startBtn.classList.add('opacity-70', 'cursor-not-allowed');
            
            // 重置所有状态
            resetSignals();
            resultContainer.classList.add('hidden');
            logContainer.innerHTML = '';
            
            // 获取输入值
            const regAddress = parseInt(regAddressInput.value);
            let writeValue = parseInt(writeValueInput.value);
            
            // 验证输入
            if (isNaN(regAddress) || regAddress < 0 || regAddress > 15) {
                addLog(`错误：无效的寄存器地址 ${regAddress}`);
                startBtn.disabled = false;
                startBtn.classList.remove('opacity-70', 'cursor-not-allowed');
                return;
            }
            
            if (operationType === 'write' && (isNaN(writeValue) || writeValue < 0 || writeValue > 255)) {
                addLog(`错误：无效的写入值 ${writeValue}`);
                startBtn.disabled = false;
                startBtn.classList.remove('opacity-70', 'cursor-not-allowed');
                return;
            }
            
            addLog(`开始${operationType === 'read' ? '读取' : '写入'}操作，寄存器地址：0x${regAddress.toString(16).toUpperCase()}`);
            
            // 步骤1：片选选中（拉低）
            highlightStep(0);
            csLine.classList.remove('bg-danger');
            csLine.classList.add('bg-green-500');
            addLog('步骤1：片选信号拉低，选中从设备');
            await new Promise(resolve => setTimeout(resolve, 800));
            
            // 高亮显示当前操作的寄存器
            document.getElementById(`reg-${regAddress}`).classList.add('register-cell-active');
            
            // 步骤2：发送寄存器地址（带读/写标志）
            highlightStep(1);
            addLog('步骤2：发送寄存器地址');
            
            // 对于读操作，地址需要设置最高位为1（SPI协议通常如此）
            const addressToSend = operationType === 'read' ? regAddress | 0x80 : regAddress;
            
            // 显示发送的地址
            mcuData.textContent = `0x${addressToSend.toString(16).toUpperCase().padStart(2, '0')}`;
            mosiLine.classList.add('bg-primary/80');
            
            // 模拟时钟信号和数据传输
            await simulateClock(sclkLine);
            
            // 从机返回一个确认值（这里简化为0x00）
            slaveData.textContent = '0x00';
            misoLine.classList.add('bg-secondary/80');
            addLog(`发送地址: 0x${addressToSend.toString(16).toUpperCase().padStart(2, '0')}，接收确认: 0x00`);
            await new Promise(resolve => setTimeout(resolve, 500));
            
            // 步骤3：交换数据（读或写）
            highlightStep(2);
            mosiLine.classList.remove('bg-primary/80');
            misoLine.classList.remove('bg-secondary/80');
            
            if (operationType === 'read') {
                addLog('步骤3：读取寄存器数据（发送dummy数据）');
                // 发送dummy数据，接收寄存器值
                mcuData.textContent = '0x00';
                mosiLine.classList.add('bg-primary/80');
                
                const regValue = registers[regAddress];
                slaveData.textContent = `0x${regValue.toString(16).toUpperCase().padStart(2, '0')}`;
                misoLine.classList.add('bg-secondary/80');
                
                await simulateClock(sclkLine);
                addLog(`发送: 0x00 (dummy)，接收寄存器值: 0x${regValue.toString(16).toUpperCase().padStart(2, '0')}`);
                
                // 显示结果
                resultContainer.classList.remove('hidden');
                resultText.innerHTML = `成功读取寄存器 REG${regAddress} 的值: <span class="text-primary font-bold">0x${regValue.toString(16).toUpperCase().padStart(2, '0')}</span>`;
            } else {
                addLog('步骤3：发送要写入的数据');
                // 发送要写入的值，接收旧值
                mcuData.textContent = `0x${writeValue.toString(16).toUpperCase().padStart(2, '0')}`;
                mosiLine.classList.add('bg-primary/80');
                
                const oldValue = registers[regAddress];
                slaveData.textContent = `0x${oldValue.toString(16).toUpperCase().padStart(2, '0')}`;
                misoLine.classList.add('bg-secondary/80');
                
                await simulateClock(sclkLine);
                addLog(`发送写入值: 0x${writeValue.toString(16).toUpperCase().padStart(2, '0')}，接收旧值: 0x${oldValue.toString(16).toUpperCase().padStart(2, '0')}`);
                
                // 更新寄存器值
                registers[regAddress] = writeValue;
                document.getElementById(`reg-${regAddress}`).innerHTML = `
                    <div class="text-xs text-gray-500">REG${regAddress}</div>
                    <div class="font-mono font-bold">0x${writeValue.toString(16).toUpperCase().padStart(2, '0')}</div>
                `;
                
                // 显示结果
                resultContainer.classList.remove('hidden');
                resultText.innerHTML = `成功向寄存器 REG${regAddress} 写入值: <span class="text-secondary font-bold">0x${writeValue.toString(16).toUpperCase().padStart(2, '0')}</span>`;
            }
            
            await new Promise(resolve => setTimeout(resolve, 500));
            
            // 步骤4：取消片选（拉高）
            highlightStep(3);
            mosiLine.classList.remove('bg-primary/80');
            misoLine.classList.remove('bg-secondary/80');
            csLine.classList.remove('bg-green-500');
            csLine.classList.add('bg-danger');
            addLog('步骤4：片选信号拉高，结束通信');
            
            // 重置数据显示
            mcuData.textContent = '0x00';
            slaveData.textContent = '0x00';
            
            await new Promise(resolve => setTimeout(resolve, 800));
            
            // 移除寄存器高亮
            document.getElementById(`reg-${regAddress}`).classList.remove('register-cell-active');
            
            addLog(`${operationType === 'read' ? '读取' : '写入'}操作完成`);
            
            // 重新启用按钮
            startBtn.disabled = false;
            startBtn.classList.remove('opacity-70', 'cursor-not-allowed');
        }
        
        // 重置信号状态
        function resetSignals() {
            csLine.className = 'signal-line bg-danger w-full';
            sclkLine.className = 'signal-line bg-gray-400 w-full';
            mosiLine.className = 'signal-line bg-primary w-full';
            misoLine.className = 'signal-line bg-secondary w-full';
            mcuData.textContent = '0x00';
            slaveData.textContent = '0x00';
            
            steps.forEach(step => {
                step.classList.remove('spi-step-active');
                step.querySelector('span').classList.add('bg-gray-200');
                step.querySelector('span').classList.remove('bg-primary', 'text-white');
            });
            
            // 移除所有寄存器高亮
            document.querySelectorAll('.register-cell-active').forEach(el => {
                el.classList.remove('register-cell-active');
            });
        }
        
        // 事件监听
        readBtn.addEventListener('click', () => {
            operationType = 'read';
            readBtn.classList.add('ring-2', 'ring-offset-2', 'ring-primary');
            writeBtn.classList.remove('ring-2', 'ring-offset-2', 'ring-primary');
            writeValueContainer.classList.add('hidden');
            step3Text.textContent = '发送 dummy 数据 (0x00)，同时接收寄存器的值 (通过MISO线)';
        });
        
        writeBtn.addEventListener('click', () => {
            operationType = 'write';
            writeBtn.classList.add('ring-2', 'ring-offset-2', 'ring-primary');
            readBtn.classList.remove('ring-2', 'ring-offset-2', 'ring-primary');
            writeValueContainer.classList.remove('hidden');
            step3Text.textContent = '发送要写入的值 (通过MOSI线)，同时接收寄存器的旧值 (通过MISO线)';
        });
        
        startBtn.addEventListener('click', simulateSPI);
        
        resetBtn.addEventListener('click', () => {
            // 重新初始化寄存器
            for (let i = 0; i < 16; i++) {
                registers[i] = Math.floor(Math.random() * 256);
            }
            initRegisters();
            resetSignals();
            logContainer.innerHTML = '';
            resultContainer.classList.add('hidden');
        });
        
        // 初始化页面
        initRegisters();
        readBtn.click(); // 默认选中读操作
    </script>
</body>
</html>
